﻿using System;
using System.Transactions;

namespace Miado
{
    /// <summary>
    /// This class serves as a wrapper around an ADO.Net transaction.  
    /// </summary>
    [Serializable]
    public static class Transaction
    {
        [ThreadStatic]
        private static int _transactionCount;

        /// <summary>
        /// Gets a value indicating whether this instance is currently 
        /// participating in a Transaction.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is in a Transaction; otherwise, <c>false</c>.
        /// </value>
        internal static bool IsInTransaction
        {
            get
            {
                return _transactionCount > 0;
            }
        }

        /// <summary>
        /// Wraps a database action around a Transaction.  The TransactionScopeOption
        /// is set to "Required", which means the database action will run 
        /// under the current Transaction if one is present.  If not, a new 
        /// Transaction will be created.
        /// </summary>
        /// <param name="dbAction">The database action.</param>
        public static void Required(Action dbAction)
        {
            New(TransactionScopeOption.Required, dbAction);
        }

        /// <summary>
        /// Wraps a database action around a Transaction.  The TransactionScopeOption
        /// is set to "RequiresNew", which means the database action will run 
        /// under a new Transaction (even if one is currently present).
        /// </summary>
        /// <param name="dbAction">The database action.</param>
        public static void RequiresNew(Action dbAction)
        {
            New(TransactionScopeOption.RequiresNew, dbAction);
        }

        /// <summary>
        /// Suppresses the specified database action from participating in a Transaction
        /// (if one is currently present).
        /// </summary>
        /// <param name="dbAction">The database action.</param>
        public static void Suppress(Action dbAction)
        {
            New(TransactionScopeOption.Suppress, dbAction);
        }

        /// <summary>
        /// Wraps a database action around a Transaction.  The TransactionScopeOption
        /// that will be used is determined by the method.
        /// </summary>
        /// <param name="txOption">The transaction option.</param>
        /// <param name="dbAction">The database action.</param>
        public static void New(TransactionScopeOption txOption, Action dbAction)
        {
            using ( var tx = new TransactionScope(txOption) )
            {
                try
                {
                    IncrementTransactionCount();
                    dbAction();
                    tx.Complete();
                }
                finally
                {
                    DecrementTransactionCountAndCloseConnections();
                }
            }
        }

        /// <summary>
        /// Executes a database action using a Transaction.  If the 
        /// current Thread is already participating in a Transaction, 
        /// this action will simply be executed.  However, if no 
        /// Transaction is currently present, a new one will be created
        /// and then the action will executed within the scope of the
        /// Transaction.
        /// </summary>
        /// <param name="dbAction">The database action.</param>
        internal static void ImplicitlyRequired(Action dbAction)
        {
            if ( IsInTransaction )
            {
                dbAction();
            }
            else
            {
                Required(dbAction);
            }
        }

        /// <summary>
        /// Increments the transaction count.
        /// </summary>
        private static void IncrementTransactionCount()
        {
            _transactionCount++;
        }

        /// <summary>
        /// Decrements the transaction count and close connections.
        /// </summary>
        private static void DecrementTransactionCountAndCloseConnections()
        {
            _transactionCount--;

            // no more work - close all the connections
            if ( _transactionCount == 0 )
            {
                ConnectionManager.ReleaseConnections();
            }
        }
    }
}
